/*
*Copyright 2021-2023 NERCIS
*
*Licensed under the Apache License, Version 2.0 (the "License");
*you may not use this file except in compliance with the License.
*You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*Unless required by applicable law or agreed to in writing, software
*distributed under the License is distributed on an "AS IS" BASIS,
*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*See the License for the specific language governing permissions and
*limitations under the License.
*/

package cn.ac.nercis.pes.service.project;

import cn.ac.nercis.pes.common.constant.project.SystemState;
import cn.ac.nercis.pes.model.dto.common.Result;
import cn.ac.nercis.pes.model.dto.common.ResultCode;
import cn.ac.nercis.pes.model.dto.project.SystemDTO;
import cn.ac.nercis.pes.model.dto.project.SystemDetailDTO;
import cn.ac.nercis.pes.model.dto.project.SystemEvaluationDTO;
import cn.ac.nercis.pes.repository.dal.evaluation.EvaluationRecordRepository;
import cn.ac.nercis.pes.repository.dal.project.ProjectRepository;
import cn.ac.nercis.pes.repository.dal.project.SystemProcessRepository;
import cn.ac.nercis.pes.repository.dal.project.SystemRepository;
import cn.ac.nercis.pes.repository.mapper.project.SystemMapper;
import cn.ac.nercis.pes.repository.model.project.ProjectDO;
import cn.ac.nercis.pes.repository.model.project.SystemDO;
import cn.ac.nercis.pes.service.evaluation.AssetService;
import cn.ac.nercis.pes.service.evaluation.EvaluationRequireService;
import cn.ac.nercis.pes.service.evaluation.SecurityThreatService;
import cn.ac.nercis.pes.service.manage.ProcessRelatedService;
import cn.ac.nercis.pes.service.document.report.ReportOverallEvaluationDataService;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 系统管理服务
 *
 * @author zpy
 * @version 1.0.0
 */
@Slf4j
@Service
public record SystemService(SystemRepository systemRepository, ProjectRepository projectRepository,
                            SystemProcessRepository processRepository, AssetService assetService,
                            SystemTargetService systemTargetService, SecurityThreatService securityThreatService,
                            EvaluationRecordRepository evaluationRecordRepository,
                            EvaluationRequireService evaluationRequireService,
                            ProcessRelatedService processRelatedService,
                            SystemEvaluationService systemEvaluationService,
                            ReportOverallEvaluationDataService overallEvaluationDataService) {
    private final static String LAST_SYSTEM_SORT_FIELD = "createTime";

    private static final String SYSTEM_RESULT = "%s(%s分)";

    @Autowired
    public SystemService {
    }

    public List<SystemDTO> findAll(String projectId) {
        return this.systemRepository.findAllByProjectId(projectId)
                .stream()
                .map(SystemMapper.INSTANCE::toDTO)
                .collect(Collectors.toList());
    }

    public Page<SystemDTO> findAll(String systemName, Pageable pageable) {
        SystemDO example = SystemDO.builder().name(systemName).build();
        ExampleMatcher matcher = ExampleMatcher.matchingAny()
                .withMatcher("name", ExampleMatcher.GenericPropertyMatchers.contains());
        return this.systemRepository.findAll(Example.of(example, matcher), getSystemPageRequest(pageable))
                .map(SystemMapper.INSTANCE::toDTO);
    }

    public Page<SystemDTO> findAllByProjectId(String projectId, Pageable pageable){
        SystemDO example = SystemDO.builder().project(ProjectDO.builder().id(projectId).build()).build();
        ExampleMatcher matcher = ExampleMatcher.matchingAny()
                .withMatcher("name", ExampleMatcher.GenericPropertyMatchers.contains());
        return this.systemRepository.findAll(Example.of(example, matcher), getSystemPageRequest(pageable))
                .map(entity -> {
                    var dto = SystemMapper.INSTANCE.toDTO(entity);
                    String evalResult = overallEvaluationDataService.getEvalResult(entity);
                    String totalScore = overallEvaluationDataService.getOverallScore(entity);
                    dto.setResult(String.format(SYSTEM_RESULT, evalResult, totalScore));
                    return dto;
                });
    }

    private PageRequest getSystemPageRequest(Pageable pageable){
        return PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(),
                Sort.by(Sort.Direction.DESC, LAST_SYSTEM_SORT_FIELD));
    }

    public Result<SystemDetailDTO> findById(String systemId) {
        var evaluation = this.systemEvaluationService.getSystemEvaluation(systemId);
        return this.systemRepository.findById(systemId)
                .map(s->SystemMapper.INSTANCE.toDetailDTO(s,evaluation))
                .map(Result::success)
                .orElse(Result.failed(ResultCode.SYSTEM_NOT_EXIST));
    }

    public Boolean validSystemExists(String systemId) {
        return this.systemRepository.existsById(systemId);
    }

    public SystemState getSystemState(String systemId) {
        return this.systemRepository.findById(systemId)
                .map(SystemDO::getState)
                .orElse(SystemState.DEFAULT);
    }

    public Result<SystemState> setSystemState(String systemId, SystemState state) {
        SystemDO systemDO = this.systemRepository.findById(systemId).orElse(null);
        if (Objects.isNull(systemDO)) {
            return Result.failed(ResultCode.SYSTEM_NOT_EXIST);
        }
        return Optional.of(systemDO)
                .map(s -> {
                    s.setState(state);
                    return s;
                })
                .map(this.systemRepository::save)
                .map(SystemDO::getState)
                .map(Result::success)
                .orElse(Result.failed(ResultCode.FAIL));
    }

    public Result<SystemDTO> save(SystemDetailDTO systemDetail) {
        try {
            if (StringUtils.isEmpty(systemDetail.getId())) {
                return create(systemDetail);
            } else {
                return modify(systemDetail);
            }
        } catch (Exception e) {
            log.error("保存系统出错：", e);
            return Result.failed(ResultCode.FAIL);
        }
    }

    public Result<String> checkEvalRecord(@NonNull String systemId) {
        boolean hasRelated = this.evaluationRecordRepository.existsBySystemId(systemId);
        if (hasRelated) {
            return Result.failed("当前系统存在测评数据，是否继续进行删除？");
        } else {
            return Result.success();
        }
    }

    public Result<String> delete(final String id) {
        try {
            processRelatedService.deleteSystemRelatedData(id);
            return systemRepository.findById(id)
                    .map(systemDO -> {
                        systemRepository.delete(systemDO);
                        return systemDO;
                    })
                    .map(systemDO -> Result.success(systemDO.getId()))
                    .orElse(Result.failed(ResultCode.FAIL));
        } catch (Exception ex) {
            log.error("删除系统出错：", ex);
            return Result.failed(ResultCode.FAIL);
        }
    }

    private Result<SystemDTO> create(SystemDetailDTO systemDetail) {
        List<SystemDO> systemLists = this.systemRepository.findAllByProjectId(systemDetail.getProjectId());
        if (systemLists.stream().anyMatch(s -> StringUtils.equals(s.getName(), systemDetail.getName()))) {
            return Result.failed(ResultCode.SYSTEM_NAME_EXIST);
        }
        if (Objects.isNull(systemDetail.getSystemEvaluation())) {
            systemDetail.setSystemEvaluation(new SystemEvaluationDTO());
        }
        var projectDO = this.projectRepository.findById(systemDetail.getProjectId());
        return projectDO.map(pd -> Optional.of(SystemMapper.INSTANCE.toDO(systemDetail))
                        .map(sd -> {
                            sd.setProject(pd);
                            sd.setState(SystemState.DEFAULT);
                            sd.setSort(systemLists.size() + 1);
                            sd.setCreateTime(LocalDateTime.now());
                            return sd;
                        })
                        .map(systemRepository::save)
                        .map(systemDO -> {
                            systemEvaluationService.initSystemEvaluation(systemDO,SystemMapper.INSTANCE.toEvaluationDO(systemDetail.getSystemEvaluation()));
                            systemTargetService.initSystemTarget(systemDO);
                            securityThreatService.initSecurityThreat(systemDO.getId());
                            assetService.generateDefaultSystemAsset(systemDO.getId());
                            evaluationRequireService.initEvaluationRequire(systemDO);
                            return systemDO;
                        })
                        .map(SystemMapper.INSTANCE::toDTO)
                        .map(Result::success)
                        .orElse(Result.failed(ResultCode.FAIL)))
                .orElse(Result.failed(ResultCode.PROJECT_NOT_EXIST));
    }

    private Result<SystemDTO> modify(SystemDetailDTO systemDetail) {
        var systemLists = this.systemRepository.findAllByProjectId(systemDetail.getProjectId());
        var systemDO = systemLists.stream().filter(s -> StringUtils.equals(s.getId(), systemDetail.getId()))
                .findAny().orElse(null);
        if (Objects.isNull(systemDO)) {
            return Result.failed(ResultCode.SYSTEM_NOT_EXIST);
        }
        if (!StringUtils.equals(systemDO.getName(), systemDetail.getName())
                && systemLists.stream().anyMatch(s -> StringUtils.equals(s.getName(), systemDetail.getName()))) {
            return Result.failed(ResultCode.SYSTEM_NAME_EXIST);
        }
        //判断安全级别是否变更
        if (systemDO.getSecurityLevel() != systemDetail.getSecurityLevel()) {
            return Result.failed("不允许修改系统测评基础信息安全等级");
        }
        var evaluationResult = systemEvaluationService.save(systemDO.getId(),SystemMapper.INSTANCE.toEvaluationDO(systemDetail.getSystemEvaluation()));
        if(!evaluationResult.isSuccess()){
            return Result.failed("系统测评基础信息修改失败");
        }
        return Optional.of(systemDO)
                .map(s -> {
                    s.setName(systemDetail.getName());
                    s.setSecurityLevel(systemDetail.getSecurityLevel());
                    s.setSLevel(systemDetail.getSLevel());
                    s.setALevel(systemDetail.getALevel());
                    s.setGLevel(systemDetail.getGLevel());
                    s.setReqLevel(systemDetail.getReqLevel());
                    s.setIsFiling(systemDetail.getIsFiling());
                    s.setFilingNumber(systemDetail.getFilingNumber());
                    s.setDesc(systemDetail.getDesc());
                    s.setModifyTime(LocalDateTime.now());
                    return s;
                })
                .map(systemRepository::save)
                .map(SystemMapper.INSTANCE::toDTO)
                .map(Result::success)
                .orElse(Result.failed(ResultCode.FAIL));
    }
}